using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.IO;
using Commons.Models.Settings;
using Commons.Transactions;
using System.Data.SqlClient;

namespace Commons.Daos
{
	public delegate void PersistenceCommand();

	public class PersistenceManager : IDisposable, ITransactional, IPersistenceManager
	{
		private Dictionary<object, PersistenceCommand> commands = new Dictionary<object, PersistenceCommand>();
		private SqlConnection connection;
		private DbTransaction transaction;

		public List<ISQLDao> SQLDaos { protected get; set; }
		public List<ICacheControl> CacheControls { protected get; set; }

		public static bool Exists()
		{
			return File.Exists(GetDatabaseFilename());
		}

		public static string GetDatabaseFilename()
		{
			return Path.Combine(GameSettings.PersistentDataPath, GameSettings.DatabaseFilename);
		}

        public SqlConnection GetConnection()
		{
			if (connection == null)
			{
				string path = GetDatabaseFilename();
				bool exists = File.Exists(path);

				// Connect
				connection = new SqlConnection("URI=file:" + path);
				connection.Open();
				connection.Disposed += delegate { connection = null; };
				connection.StateChange += delegate(object sender, StateChangeEventArgs e) {
					Debug.LogFormat("Database stateChange: {0} -> {1}", e.OriginalState, e.CurrentState);
					if (connection != null && (e.CurrentState == ConnectionState.Closed || e.CurrentState == ConnectionState.Broken))
					{
						Debug.LogFormat("Going to dispose ... ");
						connection.Dispose();
					}
				};

				PrepareDatabase(path, exists);
			}

			return connection;
		}
        protected int databaseVersion = 1;
		protected int DatabaseVersion
		{
            get { return databaseVersion; }
            set { databaseVersion = value; } 
            /*
			get
			{
				using (SqlCommand command = new SqlCommand("select version from metadata", connection))
				{
					try
					{
						return Convert.ToInt32(command.ExecuteScalar());
					}
					finally
					{
						command.Finalize();
					}
				}
			}
			set
			{
				using (SqlCommand command = new SqlCommand("update metadata set version = " + value, connection))
				{
					command.ExecuteNonQuery();
					command.Finalize();
				}
			}
            */
		}

		protected void PrepareDatabase(string path, bool exists)
		{
            if (!exists)
			{
				try
				{
					using (SqlCommand command = new SqlCommand("create table metadata (version integer)", connection))
					{
						command.ExecuteNonQuery();
						command.Dispose();
					}
					using (SqlCommand command = new SqlCommand("insert into metadata (version) values (0)", connection))
					{
						command.ExecuteNonQuery();
						command.Dispose();
					}
				}
				catch (Exception e)
				{
					if (File.Exists(path))
					{
						Dispose();

						File.Delete(path);
					}

					throw e;
				}
			}
            
			PrepareTables();
		}

		protected void PrepareTables()
		{
			// Upgrade tables
			int oldVersion = DatabaseVersion; // current version
			int newVersion = GameSettings.DatabaseVersion; // required version
			if (oldVersion < newVersion)
			{
				DbTransaction tx = connection.BeginTransaction();

				try
				{
					foreach (ISQLDao dao in SQLDaos)
					{
						dao.UpgradeTable(oldVersion);
					}

					DatabaseVersion = newVersion;

					tx.Commit();
				}
				catch (Exception e)
				{
					tx.Rollback();

					throw e;
				}
			}
		}

		public void AddCommand(object data, PersistenceCommand command)
		{
			if (transaction == null)
			{
				command();
			}
			else
			{
				commands[data] = command;
			}
		}

		public void Flush()
		{
			foreach (PersistenceCommand command in commands.Values)
			{
				command();
			}
			commands.Clear();
		}

		public void ClearCaches()
		{
			foreach (ICacheControl cacheControl in CacheControls)
			{
				cacheControl.ClearCache();
			}
		}

		public void Close()
		{
			if (connection != null)
			{
				connection.Close();
				connection = null;
			}
		}

		public void Dispose()
		{
			Close();
		}

		public void BeginTransaction()
		{
			transaction = GetConnection().BeginTransaction();
		}

		public void Commit()
		{
			Flush();

			transaction.Commit();
			transaction.Dispose();
			transaction = null;
		}

		public void Rollback()
		{
			commands.Clear();

			transaction.Rollback();
			transaction.Dispose();
			transaction = null;

			ClearCaches();
		}
	}
}